// Player.java
package com.mytech.cslive;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.EventListener;
import java.util.Timer;
import java.util.TimerTask;

import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.mytech.cslive.commands.PlayerDataCommand;
import com.mytech.cslive.communication_handlers.ConnectionHandler;
import com.mytech.cslive.communication_handlers.UDPConnectionHandler;
import com.mytech.cslive.infrastructure.AppConfig;
import com.mytech.cslive.infrastructure.LogFile;
import com.mytech.cslive.services.LaserKitService;
import com.mytech.cslive.services.LocationStatusService;

public class Player {
	public interface ShowPlayerStatusListener extends EventListener {
		public void showPlayerStatus(PlayerStatus status);
	}

	public interface ShowPlayerMessagesListener extends EventListener {
		/**
		 * Show a short message
		 * 
		 * @param message
		 */
		public void showPlayerMessage(String message);

		/**
		 * Show a message
		 * 
		 * @param message
		 * @param longViewLength
		 *            Choose view length - long/short
		 */
		public void showPlayerMessage(String message, boolean longViewLength);
	}

	private final static String PROPERTY_SPAWN_POINT_RADIUS = "SpawnPointRadius";
	private final static String PROPERTY_MY_FLAG_RADIUS = "MyFlagRadius";
	private final static String PROPERTY_MAX_HEALTH_POINTS = "MaxHealthPoints";

	private final static String PROPERTY_COMMANDER_IP_BLUE = "CommanderIPBlue";
	private final static String PROPERTY_COMMANDER_IP_RED = "CommanderIPRed";
	private final static String PROPERTY_INITIAL_PLAYER_PORT = "InitialPlayerPort";
	private final static String PROPERTY_SPAWN_POINT_LOCATION = "SpawnPointLocation";
	private final static String PROPERTY_MY_FLAG_LOCATION = "MyFlagLocation";

	private final static int PROPERTY_INITIAL_PLAYER_PORT_VALUE = 5000;

	private final static int PROPERTY_MAX_HEALTH_POINTS_VALUE = 10;
	private final static double PROPERTY_SPAWN_POINT_RADIUS_VALUE = 0.000000000000009;
	private final static double PROPERTY_MY_FLAG_RADIUS_VALUE = 0.000000000000009;
	private final static int SPAWN_POINT_TIMER_INTERVAL_MS = 1000;
	private final static int SPAWN_POINT_TIMER_COUNT = 40;
	private final static int SPAWN_POINT_TOAST_INTERVAL = 5;

	public static final String[] ID_VALUES = {"1","2","3","4","5","6"};
	
	public static final String TAG = "CSLive.Player";

	private int id;
	private PlayerStatus status;
	private Team team;
	private LocationStatusService locationStatusService;
	private LaserKitService laserKitService;

	private int healthPoints;
	private Point2D currentLocation;
	private Timer spawnPointTimer;
	private SpawnPointTimerTask spawnPointTimerTask;

	// Listeners
	private ShowPlayerStatusListener showPlayerStatusListener;
	private ShowPlayerMessagesListener showPlayerMessagesListener;

	// Handles connection with services
	private Handler locationChangedHandler;
	private Handler gotHitHandler;

	// Handles connection with commander
	private ConnectionHandler connectionHandler;

	public Player() {
		this.setInitialConfigurations();
		
		this.setStatus(PlayerStatus.ALIVE);

		this.tracePlayer();
		
		this.startPlayerHealthService();
		
		// Create connection handlers on game engage
	}

	public ShowPlayerStatusListener getShowPlayerStatusListener() {
		return showPlayerStatusListener;
	}

	public void setShowPlayerStatusListener(
			ShowPlayerStatusListener showPlayerStatusListener) {
		this.showPlayerStatusListener = showPlayerStatusListener;
	}

	public ShowPlayerMessagesListener getShowPlayerMessagesListener() {
		return showPlayerMessagesListener;
	}

	public void setShowPlayerMessagesListener(
			ShowPlayerMessagesListener showPlayerMessagesListener) {
		this.showPlayerMessagesListener = showPlayerMessagesListener;
	}

	public static String getPropertyMyFlagRadius() {
		return PROPERTY_MY_FLAG_RADIUS;
	}

	public static String getPropertyMaxHealthPoints() {
		return PROPERTY_MAX_HEALTH_POINTS;
	}

	public static String getPropertySpawnPointRadius() {
		return PROPERTY_SPAWN_POINT_RADIUS;
	}

	public static String getPropertyMyFlagLocation() {
		return PROPERTY_MY_FLAG_LOCATION;
	}

	public static String getPropertySpawnPointLocation() {
		return PROPERTY_SPAWN_POINT_LOCATION;
	}

	public static String getPropertyCommanderIpBlue() {
		return PROPERTY_COMMANDER_IP_BLUE;
	}

	public static String getPropertyCommanderIpRed() {
		return PROPERTY_COMMANDER_IP_RED;
	}

	public static String getPropertyInitialPlayerPort() {
		return PROPERTY_INITIAL_PLAYER_PORT;
	}

	public static int getSpawnPointTimerCount() {
		return SPAWN_POINT_TIMER_COUNT;
	}

	public Timer getSpawnPointTimer() {
		return spawnPointTimer;
	}

	public SpawnPointTimerTask getSpawnPointTimerTask() {
		return spawnPointTimerTask;
	}
	
	public void setSpawnPointTimerTask(SpawnPointTimerTask spawnPointTimerTask) {
		this.spawnPointTimerTask = spawnPointTimerTask;
	}
	
	public static String[] getIdValues() {
		return ID_VALUES;
	}
	
	public void setSpawnPointTimer(Timer spawnPointTimer) {
		this.spawnPointTimer = spawnPointTimer;
	}

	public static int getSpawnPointTimerIntervalMs() {
		return SPAWN_POINT_TIMER_INTERVAL_MS;
	}

	public int getHealthPoints() {
		return healthPoints;
	}

	public void setHealthPoints(int healthPoints) {
		this.healthPoints = healthPoints;
	}

	public int getId() {
		return id;
	}

	public PlayerStatus getStatus() {
		return status;
	}

	public Team getTeam() {
		return team;
	}

	public static int getSpawnPointToastInterval() {
		return SPAWN_POINT_TOAST_INTERVAL;
	}
	
	public LocationStatusService getLocationStatusService() {
		return locationStatusService;
	}

	public LaserKitService getLaserKitService() {
		return laserKitService;
	}

	public ConnectionHandler getConnectionHandler() {
		return connectionHandler;
	}

	/**
	 * Returning the current location
	 * 
	 * @return The current location
	 */
	public Point2D getCurrentLocation() {
		return (this.currentLocation);
	}

	public void setCurrentLocation(Point2D currentLocation) {
		this.currentLocation = currentLocation;
	}

	public Handler getLocationChangedHandler() {
		return locationChangedHandler;
	}

	private void setLocationChangedHandler(Handler locationChangedHandler) {
		this.locationChangedHandler = locationChangedHandler;
	}

	public Handler getGotHitHandler() {
		return gotHitHandler;
	}

	private void setGotHitHandler(Handler gotHitHandler) {
		this.gotHitHandler = gotHitHandler;
	}

	public void setStatus(PlayerStatus status) {
		PlayerStatus previousStatus = this.getStatus();
		
		// Set new status
		this.status = status;
		
		// If status changed
		if (previousStatus != null && !previousStatus.equals(this.getStatus())) {
			// Send new player data
			try {
				this.sendPlayerData();
			} catch (IOException ex) {
				LogFile.log(ex);
			}
		}

		// If status is alive
		if (status.equals(PlayerStatus.ALIVE)) {
			// Initialize health points
			this.setHealthPoints(Integer.parseInt(AppConfig
					.getProperty(getPropertyMaxHealthPoints())));
		}
		
		// Update listener
		if (this.getShowPlayerStatusListener() != null) {
			this.getShowPlayerStatusListener().showPlayerStatus(
					status);
		}
	}

	public void setId(int id) {
		this.id = id;
	}

	public void setTeam(Team team) {
		this.team = team;
	}

	private void setInitialConfigurations() {
		// Set initial player port
		AppConfig.setProperty(PROPERTY_INITIAL_PLAYER_PORT,
				PROPERTY_INITIAL_PLAYER_PORT_VALUE);

		AppConfig.setProperty(PROPERTY_MAX_HEALTH_POINTS,
				PROPERTY_MAX_HEALTH_POINTS_VALUE);

		AppConfig.setProperty(PROPERTY_SPAWN_POINT_RADIUS,
				PROPERTY_SPAWN_POINT_RADIUS_VALUE);

		AppConfig.setProperty(PROPERTY_MY_FLAG_RADIUS,
				PROPERTY_MY_FLAG_RADIUS_VALUE);
	}
	
	private boolean isValidParameter(String parameter) { 
		return (parameter != null && !parameter.equals(""));
	}
	
	private void createConnectionHandler() throws UnknownHostException,
			SocketException, SettingsNotFilledException {
		String blueCommanderIP = AppConfig
				.getProperty(getPropertyCommanderIpBlue());
		String redCommanderIP = AppConfig
				.getProperty(getPropertyCommanderIpRed());
		String playerPort = AppConfig
				.getProperty(getPropertyInitialPlayerPort());
		
		try {
			if (this.isValidParameter(blueCommanderIP) && 
				this.isValidParameter(redCommanderIP)  &&
					this.isValidParameter(playerPort)) {
				int playerPortNumber = Integer.parseInt(playerPort);

				// Add id to get the specific port
				playerPortNumber += this.getId();

				// Create socket addresses
				InetSocketAddress blueCommanderAddress = new InetSocketAddress(
						InetAddress.getByName(blueCommanderIP),
						playerPortNumber);
				InetSocketAddress redCommanderAddress = new InetSocketAddress(
						InetAddress.getByName(redCommanderIP), playerPortNumber);

				// Create a new udp connection handler
				this.connectionHandler = new UDPConnectionHandler(
						blueCommanderAddress, redCommanderAddress);
			} else {
				throw new SettingsNotFilledException();
			}
		} catch (UnknownHostException e) {
			LogFile.log(e);

			throw (e);
		} catch (SocketException e) {
			LogFile.log(e);

			throw (e);
		}
	}

	/**
	 * Start sending player data
	 * @throws SocketException 
	 * @throws UnknownHostException 
	 */
	public void activate() throws UnknownHostException, SocketException, SettingsNotFilledException {
		this.createConnectionHandler();
	}
	
	/**
	 * Tracing player location
	 */
	private void tracePlayer() {
		this.setLocationChangedHandler(new LocationChangedHandler());
	}

	private void startPlayerHealthService() {
		setGotHitHandler(new GotHitHandler());
	}

	private boolean isInPointRadius(String locationString, String radiusString) {
		Point2D spawnPointLocation = new Point2D();

		spawnPointLocation.parse(locationString);

		double spawnPointRadius = Double.parseDouble(radiusString);

		return (getCurrentLocation().isInRadius(spawnPointLocation,
				spawnPointRadius));
	}

	/**
	 * Check if the player is in the spawn point radius
	 */
	private boolean isInSpawnPointRadius() {
		return (this.isInPointRadius(
				AppConfig.getProperty(getPropertySpawnPointLocation()),
				AppConfig.getProperty(getPropertySpawnPointRadius())));
	}

	/**
	 * Check if the player is in the home flag radius
	 * 
	 * @return
	 */
	private boolean isInHomeFlagRadius() {
		return (this.isInPointRadius(
				AppConfig.getProperty(getPropertyMyFlagLocation()),
				AppConfig.getProperty(getPropertyMyFlagRadius())));
	}

	private void updateStatusAccordingToLocation() {
		// If player is dead and in spawn point
		if (this.getStatus().equals(PlayerStatus.DEAD)
				&& this.isInSpawnPointRadius()) {
			if (this.getSpawnPointTimer() == null) {
				// Update status
				this.setStatus(PlayerStatus.IN_SPAWN_POINT);

				this.setSpawnPointTimer(new Timer(true));
				this.setSpawnPointTimerTask(new SpawnPointTimerTask());
				
				// Start timer
				this.getSpawnPointTimer().schedule(this.getSpawnPointTimerTask(),
						getSpawnPointTimerIntervalMs());
			}
		}

		// If player has flag and returned it to his home flag
		if (this.getStatus().equals(PlayerStatus.HAS_FLAG)
				&& this.isInHomeFlagRadius()) {
			// Update status
			this.setStatus(PlayerStatus.FLAG_RETURNED);

			// Show message
			this.getShowPlayerMessagesListener().showPlayerMessage(
					"You WON !!! :)", true);
		}
	}

	private void sendPlayerData() throws IOException { 
		if (getConnectionHandler() != null) {
			getConnectionHandler().sendCommand(
					new PlayerDataCommand(Player.this));
			
			// Show user a message
			getShowPlayerMessagesListener().showPlayerMessage(
					"Player data sent!\n" + 
			"x: " + getCurrentLocation().x + "\n" + 
			"y: " + getCurrentLocation().y,
				true);
		}
	}
	
	public int getSpawnPointSecondsLeft() {
		if (this.getSpawnPointTimerTask() != null) {
			return (this.getSpawnPointTimerTask().getCounter());
		} else {
			return -1;
		}
	}
	
	protected class LocationChangedHandler extends Handler {
		private final int EXCEPTION_COUNTER_LIMIT = 3;
		private int exceptionCounter;
		
		public LocationChangedHandler() {
			this.exceptionCounter = 0;
		}
		
		public int getExceptionCounterLimit() {
			return EXCEPTION_COUNTER_LIMIT;
		}
		
		public int getExceptionCounter() {
			return exceptionCounter;
		}
		
		public void setExceptionCounter(int exceptionCounter) {
			this.exceptionCounter = exceptionCounter;
		}
		
		@Override
		public void handleMessage(Message msg) {
			// Not sure if need super
			super.handleMessage(msg);

			Bundle dataBundle = msg.getData();
			Location location = (Location) dataBundle
					.get(LocationStatusService.LOCATION_KEY);

			if (location != null) {
				String geoLat = String.valueOf(location.getLatitude());
				String geoLng = String.valueOf(location.getLongitude());

				// Save current location
				setCurrentLocation(new Point2D(location.getLatitude(),
						location.getLongitude()));

				// Update status according to the current location
				updateStatusAccordingToLocation();

				try {
					// Send player data to blue and red commanders
					sendPlayerData();

					// Print information to log
					Log.d(TAG, "Latitude is: " + geoLat + " \n"
							+ "Longitude is: " + geoLng);
				} catch (IOException e) {
					// Write to log
					LogFile.log(e);
					
					this.setExceptionCounter(this.getExceptionCounter() + 1);
					
					if (this.getExceptionCounter() >= this.getExceptionCounterLimit()) {
						getShowPlayerMessagesListener().showPlayerMessage(
								"Error in sending player data commands!", true);
					}
				}
			}
		}
	}

	protected class GotHitHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			// Not sure if we need super
			super.handleMessage(msg);

			if (msg.arg1 == 1) {
				Log.d(TAG, "GOT HIT !!!!");

				// Update health points
				setHealthPoints(getHealthPoints() - 1);

				if (getHealthPoints() <= 0) {
					setStatus(PlayerStatus.DEAD);
				}
			}
		}
	}

	protected class SpawnPointTimerTask extends TimerTask {
		// Time counter
		private int counter;

		public SpawnPointTimerTask() {
			this.counter = getSpawnPointTimerCount();
		}

		public int getCounter() {
			return counter;
		}

		public void setCounter(int counter) {
			this.counter = counter;
		}

		@Override
		public void run() {
			// Only decrease counter if the player is in the spawn point
			if (isInSpawnPointRadius()) {
				this.setCounter(this.getCounter() - 1);

				if (this.getCounter() <= 0) {
					// Set status back to alive
					setStatus(PlayerStatus.ALIVE);

					// Update UI
					getShowPlayerMessagesListener().showPlayerMessage(
							"Return back to game!!", true);
				} else {
					// Show message every 5 intervals
					if (this.getCounter() % getSpawnPointToastInterval() == 0) {
						// Update UI
						getShowPlayerMessagesListener().showPlayerMessage(
								counter + " seconds left in spawn point!");
					}
				}
			} else {
				// Update UI
				getShowPlayerMessagesListener().showPlayerMessage(
						"Get back in the spawn point !! (" + counter
								+ " seconds left)", true);
			}
		}
	}

	/**
	 * Releasing the addresses for binding later
	 */
	public void releaseAddresses() {
		this.getConnectionHandler().releaseAddresses();
		
	}
}
